#include <string.h>
#include <assert.h>
#include "json.h"


void JSONNodeFree(JSONNodePtr ptr)
	{
	if(ptr==NULL) return;
	switch(ptr->type)
		{
		case json_node_null:
		case json_node_integer:
		case json_node_floating:
		case json_node_boolean:
			break;
		case json_node_string:
			free(ptr->value.string);
			break;
		case json_node_array:
			{
			int i;
			for(i=0;i< ptr->value.array.size;++i)
				{
				JSONNodeFree(ptr->value.array.items[i]);
				}
			free(ptr->value.array.items);
			break;
			}
		case json_node_object:
			{
			JSONObjetEntryFree(ptr->value.object.entry);
			break;
			}
		default:WHERE;break;
		}
	free(ptr);
	}

extern JSONObjetEntryPtr JSONObjetEntryNew(JSONNodePtr key,JSONNodePtr value)
	{
	JSONObjetEntryPtr ptr =NULL;
	if(key==NULL || value==NULL || key->type != json_node_string)
		{
		return NULL;
		}	 
	ptr=malloc(sizeof(JSONObjetEntry));
	if(ptr==NULL) return NULL;	
	ptr->next=NULL;
	ptr->key=key;
	ptr->value=value;
	return ptr;	
	}


void JSONObjetEntryFree(JSONObjetEntryPtr ptr)
	{
	if(ptr==NULL) return;
	JSONObjetEntryFree(ptr->next);
	JSONNodeFree(ptr->key);
	JSONNodeFree(ptr->value);
	free(ptr);
	}


static
JSONNodePtr _newNode(int type)
	{
	JSONNodePtr ptr= (JSONNodePtr)calloc(1,sizeof(JSONNode));
	if(ptr==NULL)
		{
		#ifndef NDEBUG
			WHERE;
			fprintf(stderr,"Out of memory cannot allocate memory for a node of type %d\n",type);
		#endif
		return NULL;
		}
	ptr->type=type;
	return ptr;
	}
	
JSONNodePtr JSONNodeNewInteger(json_integer_t value)
	{
	JSONNodePtr ptr= _newNode(json_node_integer);
	if(ptr==NULL) return NULL;
	ptr->value.integer=value;
	return ptr;
	}

JSONNodePtr JSONNodeNewNull()
	{
	JSONNodePtr ptr= _newNode(json_node_null);
	if(ptr==NULL) return NULL;
	return ptr;
	}

JSONNodePtr JSONNodeNewFloating(json_floating_t value)
	{
	JSONNodePtr ptr= _newNode(json_node_floating);
	if(ptr==NULL) return NULL;
	ptr->value.floating=value;
	return ptr;
	}

JSONNodePtr JSONNodeNewBoolean(char value)
	{
	JSONNodePtr ptr= _newNode(json_node_boolean);
	if(ptr==NULL) return NULL;
	ptr->value.boolean=(value==1?1:0);
	return ptr;
	}

JSONNodePtr JSONNodeNewString(const char* s)
	{
	if(s==NULL) return NULL;
	return JSONNodeNewNString(s,strlen(s));
	}

JSONNodePtr JSONNodeNewNString(const char* s,int length)
	{
	
	JSONNodePtr ptr= NULL;
	if(s==NULL || length<0)
		{
		return NULL;
		}
	ptr=_newNode(json_node_string);
	if(ptr==NULL) return NULL;
	ptr->value.string=malloc((length+1)*sizeof(length));
	if(ptr->value.string==NULL)
		{
		free(ptr);
		return NULL;
		}
	memcpy(ptr->value.string,s,length*sizeof(char));
	ptr->value.string[length]=0;
	return ptr;
	}

JSONNodePtr JSONNodeObjectNew()
	{
	JSONNodePtr ptr= _newNode(json_node_object);
	if(ptr==NULL) return NULL;
	return ptr;
	}

static int _insertObject(JSONObjetEntryPtr prev,JSONObjetEntryPtr value)
	{
	
	/* duplicate key ? */
	if(strcmp(prev->key->value.string,value->key->value.string)==0)
		{
		return 0;
		}

	/* leaf ? */
	if(prev->next==NULL)
		{
		prev->next=value;
		return 1;
		}
	return _insertObject(prev->next,value);	
	}
	
int JSONNodeObjectPut(JSONNodePtr object,JSONObjetEntryPtr entry)
	{
	if(entry==NULL ||
	entry->key==NULL ||
	entry->key->type!=json_node_string ||
	entry->key->value.string==NULL ||
	entry->value==NULL ||
	object==NULL || 
	object->type!=json_node_object
	) return 0;
	if( object->value.object.entry ==NULL )
		{
		object->value.object.entry=entry;
		return 1;
		}
	return _insertObject(object->value.object.entry,entry);
	}
	
extern JSONNodePtr JSONNodeArrayNew()
	{
	JSONNodePtr ptr= _newNode(json_node_array);
	if(ptr==NULL) return NULL;
	ptr->value.array.items=NULL;
	ptr->value.array.size=0;
	return ptr;
	}

int JSONNodeArrayAppend(JSONNodePtr array,JSONNodePtr item)
	{
	JSONNodePtr* ptr=NULL;
	if(item==NULL || array==NULL || array->type!=json_node_array) return 0;
	ptr=(JSONNodePtr*)realloc(array->value.array.items,(1+array->value.array.size)*sizeof(JSONNodePtr));
	if(ptr==NULL) return 0;
	array->value.array.items=ptr;
	array->value.array.items[array->value.array.size]=item;
	array->value.array.size++;
	return 1;
	}
	
int JSONNodeArraySize(const JSONNodePtr array)
	{
	if(array==NULL || array->type!=json_node_array) return -1;
	return array->value.array.size;
	}




	
JSONNodePtr JSONNodeArrayAt(JSONNodePtr array,int index)
	{
	if(array==NULL || array->type!=json_node_array || index<0 || index>=array->value.array.size) return NULL;
	return array->value.array.items[index];
	}

void _escapeString(FILE* out,const char* s)
	{
	char* p=(char*)s;
	while(*p!=0)
		{
		switch(*p)
			{
			case '\n': fputs("\\n",out); break;
			case '\r': fputs("\\r",out); break;
			case '\t': fputs("\\t",out); break;
			case '\v': fputs("\\v",out); break;
			case '\b': fputs("\\b",out); break;
			case '\\': fputs("\\\\",out); break;
			case '\'': fputs("\\\'",out); break;
			case '\"': fputs("\\\"",out); break;
			default: fputc(*p,out); break;
			}
		++p;
		}
	}

void JSONPrint(FILE* out,const JSONNodePtr ptr)
	{
	if(out==NULL)
		{
		#ifndef NDEBUG
			WHERE;fprintf(stderr,"out stream is null\n");
		#endif
		return;
		}
	if(ptr==NULL)
		{
		#ifndef NDEBUG
			WHERE;fprintf(stderr,"ptr is null\n");
		#endif
		return;
		}
	switch(ptr->type)
		{
		case json_node_null:
			fputs("null",out);
			break;
		case json_node_integer:
			fprintf(out,JSON_INTEGER_FMT,ptr->value.integer);
			break;
		case json_node_floating:
			
			fprintf(out,JSON_FLOATING_FMT,ptr->value.floating);
			break;
		case json_node_boolean:
			fprintf(out,"%s",(ptr->value.boolean==1?"true":"false"));
			break;
		case json_node_string:
			fputc('\"',out);
			_escapeString(out,ptr->value.string);
			fputc('\"',out);
			break;
		case json_node_array:
			{
			int i;
			fputc('[',out);
			for(i=0;i< ptr->value.array.size;++i)
				{
				if(i!=0) fputc(',',out);
				JSONPrint(out, ptr->value.array.items[i]);
				}
			fputc(']',out);
			break;
			}
		case json_node_object:
			{
			int i=0;
			JSONObjetEntryPtr  entry=  ptr->value.object.entry;
			fputc('{',out);
			while(entry!=NULL)
				{
				if(i!=0) fputc(',',out);
				i++;
				JSONPrint(out,entry->key);
				fputc(':',out);
				JSONPrint(out,entry->value);
				entry=entry->next;
				}
			fputc('}',out);
			break;
			}
		default:
			{
			#ifndef NDEBUG
				WHERE;fprintf(stderr,"%d ??\n",ptr->type);
			#endif
			assert(0);
			break;
			}
		}
	}





static int _serialize(DataStream out,const JSONNodePtr ptr)
	{
	char opcode;
	if(out==NULL || ptr==NULL) return 0;
	opcode=(char)ptr->type;
	if(DataStreamAppend(out,&opcode,1)!=1)
		{
		return 0;
		}
	switch(ptr->type)
		{
		case json_node_null:
			break;
		case json_node_integer:
			if(DataStreamAppend(out,(char*)&(ptr->value.integer),sizeof(json_integer_t))!=1)
				{
				return 0;
				}
			break;
		case json_node_floating:
			if(DataStreamAppend(out,(char*)&(ptr->value.floating),sizeof(json_floating_t))!=1)
				{
				return 0;
				}
			break;
		case json_node_boolean:
			if(DataStreamAppend(out,(char*)&(ptr->value.boolean),sizeof(char))!=1)
				{
				return 0;
				}
			break;
		case json_node_string:
			{
			int length=strlen(ptr->value.string);
			if(DataStreamAppend(out,(char*)&length,sizeof(int))!=1)
				{
				return 0;
				}
			if(DataStreamAppend(out,ptr->value.string,length)!=1)
				{
				return 0;
				}
			break;
			}
		case json_node_array:
			{
			int i=ptr->value.array.size;
			if(DataStreamAppend(out,(char*)&i,sizeof(int))!=1)
				{
				return 0;
				}
			for(i=0;i< ptr->value.array.size;++i)
				{
				if(_serialize(out, ptr->value.array.items[i])!=1)
					{
					return 0;
					}
				}
			break;
			}
		case json_node_object:
			{
			int i=0;
			JSONObjetEntryPtr  entry=  ptr->value.object.entry;
			while(entry!=NULL)
				{
				i++;
				entry=entry->next;
				}
			if(DataStreamAppend(out,(char*)&i,sizeof(int))!=1)
				{
				return 0;
				}
			entry=  ptr->value.object.entry;
			while(entry!=NULL)
				{
				if(_serialize(out,entry->key)!=1)
					{
					return 0;
					}
				if(_serialize(out,entry->value)!=1)
					{
					return 0;
					}
				entry=entry->next;
				}
			break;
			}
		default:WHERE;assert(0);break;
		}
	return 1;
	}


int JSONNodeSize(const JSONNodePtr ptr)
	{
	if(ptr==NULL) return -1;
	switch(ptr->type)
		{
		case json_node_array: return ptr->value.array.size;
		case json_node_object:
			{
			int i=0;
			JSONObjetEntryPtr  entry=  ptr->value.object.entry;
			while(entry!=NULL)
				{
				i++;
				entry=entry->next;
				}
			return i;
			}
		case json_node_string: return strlen(ptr->value.string);
		default: return -1;
		}
	return -1;
	}


DataStream JSONSerialize(const JSONNodePtr ptr)
	{
	DataStream ds;
	if(ptr==NULL) return NULL;
	ds= DataStreamNew();
	if(ds==NULL) return NULL;
	if(_serialize(ds,ptr)!=1)
		{
		DataStreamFree(ds);
		return NULL;
		}
	return ds;
	}


static JSONNodePtr _deserialize(DataStreamReader in)
	{
	char opcode=0;
	if(in==NULL ) return NULL;
	if(DataStreamRead(in,&opcode,sizeof(char))!=1)
		{
		return NULL;
		}
	
	switch(opcode)
		{
		case json_node_null:
			return JSONNodeNewNull();
		case json_node_integer:
			{
			json_integer_t n=0L;
			if(DataStreamRead(in,(char*)&n,sizeof(json_integer_t))!=1)
				{
				return NULL;
				}
			return JSONNodeNewInteger(n);
			}
		case json_node_floating:
			{
			json_floating_t n=0.;
			if(DataStreamRead(in,(char*)&n,sizeof(json_floating_t))!=1)
				{
				return NULL;
				}
			return JSONNodeNewFloating(n);
			}
		case json_node_boolean:
			{
			char c=0;
			if(DataStreamRead(in,&c,sizeof(char))!=1)
				{
				return NULL;
				}
			return JSONNodeNewBoolean(c);
			}
		case json_node_string:
			{
			int n=0;
			char *p;
			JSONNodePtr r=NULL;
			if(DataStreamRead(in,(char*)&n,sizeof(int))!=1)
				{
				return NULL;
				}
			p=malloc(n);
			if(p==NULL) return NULL;
			if(DataStreamRead(in,p,n*sizeof(char))!=1)
				{
				free(p);
				return NULL;
				}
			r= JSONNodeNewNString(p,n);
			free(p);
			return r;
			};
		case json_node_array:
			{
			int i=0,n=0;
			JSONNodePtr array;
			if(DataStreamRead(in,(char*)&n,sizeof(int))!=1)
				{
				return NULL;
				}
			array=JSONNodeArrayNew();
			if(array==NULL) return NULL;
			for(i=0;i< n;++i)
				{
				if(JSONNodeArrayAppend(array,_deserialize(in))!=1)
					{
					JSONNodeFree(array);
					return NULL;
					}
				}
			return array;
			}
		case json_node_object:
			{
			int i=0,n=0;
			JSONNodePtr object;

			if(DataStreamRead(in,(char*)&n,sizeof(int))!=1)
				{
				return NULL;
				}
			object=  JSONNodeObjectNew();
			if(object==NULL) return NULL;
			for(i=0;i< n;++i)
				{
				JSONNodePtr key= _deserialize(in);
				JSONNodePtr value= _deserialize(in);
				JSONObjetEntryPtr entry= JSONObjetEntryNew(key,value);
				if(entry==NULL)
					{
					JSONNodeFree(key);
					JSONNodeFree(value);
					return NULL;
					}
				if(JSONNodeObjectPut(object,entry)!=1)
					{
					JSONObjetEntryFree(entry);
					JSONNodeFree(object);
					return NULL;
					}
				}
			return object;
			}
		default:WHERE;break;
		}
	assert(0);
	return NULL;
	}
	
JSONNodePtr JSONDeserialize(const DataStream in)
	{
	DSReaderStructure reader;
	reader.in=(DataStream)in;
	reader.position=0;
	return _deserialize(&reader);
	}

